﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using NetFrameworkExtensions.BusinessLayer.Properties;
using System.Diagnostics;
using System.ComponentModel;

namespace NetFrameworkExtensions.BusinessLayer
{
  public abstract class IdentifiedListBase<TEntity, TList, TIdentified, TIdentifier> :
    ObservableCollection<TIdentified>,
    IItemContainer<TIdentified, TIdentifier>,
    IItemContainer<TIdentifier>,
    IItemContainer,
    IFreezable,
    IFreezableInternal
    where TEntity : class, IIdentified<TIdentifier>
    where TList : IdentifiedListBase<TEntity, TList, TIdentified, TIdentifier>
    where TIdentified : IdentifiedBase<TIdentified, TIdentifier>, IFreezable
    where TIdentifier : IEquatable<TIdentifier>
  {
    #region [ ObservableCollection ]
    protected override void InsertItem(int index, TIdentified item)
    {
      CheckFrozen();
      base.InsertItem(index, item);
    }

    protected override void ClearItems()
    {
      CheckFrozen();
      base.ClearItems();
    }

    protected override void MoveItem(int oldIndex, int newIndex)
    {
      CheckFrozen();
      base.MoveItem(oldIndex, newIndex);
    }

    protected override void RemoveItem(int index)
    {
      CheckFrozen();
      base.RemoveItem(index);
    }

    protected override void SetItem(int index, TIdentified item)
    {
      CheckFrozen();
      base.SetItem(index, item);
    }
    #endregion

    #region [ IFreezable ]
    private bool _isFrozen;
    public bool IsFrozen
    {
      get { return _isFrozen; }
      set
      {
        if (value == _isFrozen) return;
        if (unfreezeCounter < 0) throw new InvalidOperationException(Resources.ExCannotFreezeObjectAtThisTime);
        CheckFrozen();
        Debug.Assert(!_isFrozen);
        Debug.Assert(value);
        if (!OnBeforeFreeze()) throw new InvalidOperationException(Resources.ExCannotFreezeObjectAtThisTime);
        FreezeInternal();
        OnFrozen();
      }
    }
    GenericMonitor _UnfreezeInternalMonitor;
    int unfreezeCounter;
    protected GenericMonitor UnfreezeInternal()
    {
      if (_UnfreezeInternalMonitor == null)
        _UnfreezeInternalMonitor = new GenericMonitor(
          () =>
          {
            if (unfreezeCounter > 0) unfreezeCounter++;
            else if (unfreezeCounter < 0) unfreezeCounter--;
            else
              if (_isFrozen)
              {
                unfreezeCounter++;
                _isFrozen = false;
              }
              else
                unfreezeCounter--;
          },
          () =>
          {
            if (unfreezeCounter == 0) throw new InvalidOperationException(); //too many dispose operations
            else if (unfreezeCounter > 0)
            {
              unfreezeCounter--;
              if (unfreezeCounter == 0)
                _isFrozen = true;
            }
            else
            {
              unfreezeCounter++;
            }
          });
      _UnfreezeInternalMonitor.Enter();
      return _UnfreezeInternalMonitor;
    }
    #endregion

    #region [ IFreezableInternal ]
    protected void CheckFrozen()
    {
      if (_isFrozen)
        throw new InvalidOperationException(FormattedResources.ExFrozenObject);
    }

    protected virtual bool OnBeforeFreeze()
    {
      return this
        .OfType<IFreezableInternal>()
        .All(i => i.OnBeforeFreeze());
    }

    protected virtual void FreezeInternal()
    {
      foreach (var item in this.OfType<IFreezableInternal>())
        item.FreezeInternal();
      _isFrozen = true;
    }

    protected virtual void OnFrozen()
    {
      foreach (var item in this.OfType<IFreezableInternal>())
        item.OnFrozen();
      OnPropertyChanged(new PropertyChangedEventArgs("IsFrozen"));
    }

    bool IFreezableInternal.OnBeforeFreeze()
    {
      return OnBeforeFreeze();
    }

    void IFreezableInternal.FreezeInternal()
    {
      FreezeInternal();
    }

    void IFreezableInternal.OnFrozen()
    {
      OnFrozen();
    }
    #endregion

    #region [ IItemContainer ]

    protected abstract TIdentified CreateChildFrom(TEntity item);

    IIdentified<TIdentified, TIdentifier> IItemContainer<TIdentified, TIdentifier>.CreateFrom(IIdentified<TIdentified, TIdentifier> item)
    {
      if (item is TEntity)
      {
        var newItem = (TIdentified)Activator.CreateInstance(typeof(TIdentified), item);
        newItem.IsFrozen = this.IsFrozen;
        using (_isFrozen.SetWithRestore(false, v => _isFrozen = v))
          this.Add(newItem);
        return newItem;
      }
      return null;
    }

    IIdentified<TIdentified, TIdentifier> IItemContainer<TIdentified, TIdentifier>.RemoveFrom(IIdentified<TIdentified, TIdentifier> item)
    {
      if (item is TEntity)
      {
        var it = (TEntity)item;
        var local = this.FirstOrDefault(l => l.Identifier.Equals(it.Identifier));
        if (local != null)
          using (_isFrozen.SetWithRestore(false, v => _isFrozen = v))
            Remove(local);
        return local;
      }
      return null;
    }

    IIdentified<TIdentifier> IItemContainer<TIdentifier>.CreateFrom(IIdentified<TIdentifier> item)
    {
      return ((IItemContainer<TIdentified, TIdentifier>)this).CreateFrom((IIdentified<TIdentified, TIdentifier>)item);
    }

    IIdentified<TIdentifier> IItemContainer<TIdentifier>.RemoveFrom(IIdentified<TIdentifier> item)
    {
      return ((IItemContainer<TIdentified, TIdentifier>)this).RemoveFrom((IIdentified<TIdentified, TIdentifier>)item);
    }

    IIdentified IItemContainer.CreateFrom(IIdentified item)
    {
      return ((IItemContainer<TIdentified, TIdentifier>)this).CreateFrom((IIdentified<TIdentified, TIdentifier>)item);
    }

    IIdentified IItemContainer.RemoveFrom(IIdentified item)
    {
      return ((IItemContainer<TIdentified, TIdentifier>)this).RemoveFrom((IIdentified<TIdentified, TIdentifier>)item);
    }
    #endregion
  }
}
